리액트 웹 앱을 개발 한다면 번들링에 주로 사용 되는 도구는 webpack
일 것입니다. webpack
의 주 역할은 js, css 등등 여러 모듈화 되어 여러 파일로 나누어진 소스 파일을 하나 또는 여러 파일로 합쳐 주어 배포에 용이하게 해줍니다.
이 과정 중에서 소스 코드들을 최적화 하여 번들(합쳐진 파일)의 용량을 작게 해주기도 합니다. 소스 코드의 양을 줄이는 최적화에는 크게 두가지가 있습니다. 코드 자체를 압축하는 방식(minify
)와 소스 코드 중 사용하지 않는 코드를 버리는 (treeShaking
) 두가지 방식이 있습니다.
minify
는 내가 작성한 소스 코드가 더 간소화 되지만 동작은 동일하게 하도록 바꾸어 줍니다.
webpack
에서는 기본적으로 production mode
로 번들링하게 되면 terser 를 이용하여 압축합니다
어떻게 terser
플러그인이 소스 코드를 최적화 하는지 아래 예시를 통해 확인 할 수 있습니다.
최적화 전
var x = {
baz_: 0,
foo_: 1,
calc: function() {
return this.foo_ + this.baz_;
}
};
x.bar_ = 2;
x["baz_"] = 3;
console.log(x.calc());
최적화 후
var x={o:(0,3),t:1,i:function(){return this.t+this.o},s:2};console.log(x.i());
동작은 동일하게 하지만 소스 코드의 파일의 용량은 크게 줄었을 것입니다.
treeShaking
은 라이브러리의 소스 코드 중 내가 사용한 부분만 import 해주어 번들에 포함되고 나머지 사용하지 않는 코드는 버려주는 매우 훌륭한 마법 같은 기능입니다.
webpack
을 소개 해주는 자료들에서 이 기능을 소개해주는 것을 보았을 때 정말 좋은 세상에서 개발하고 있구나 생각을 했습니다.
하지만..
직접 프로젝트의 번들링 최적화를 해보면서 역시나 세상은 만만치 않구나 라는 것을 알게 되었고 TreeShaking
에는 많은 제약 조건이 있다는 것을 알게 되었습니다.
Treeshaking이 가능 하려면 소스코드가 ES modules
로 빌드 되어있어야 합니다.
JS에 여러 모듈 시스템이 존재 하지만 주로 commonJS
, ES modules
두가지 방식이 주를 이루고 있습니다.
commonJS
는 const lodash = require('lodash')
처럼 require 문을 사용해 모듈을 불러오고 nodeJS에서 지원하는 모듈 시스템 이기도 합니다.ES modules
는 import lodash from 'lodash'
import 문을 사용해 모듈을 불러옵니다.webpack
에선 Treeshaking
을 하기 위한 조건으로 ES modules
로 빌드 된 소스 코드만 지원 합니다.
한마디로 내가 npm을 통해 어떤 라이브러리를 받았을때 앞서 말씀 드린 것처럼 해당 라이브러리가 ES module
로 빌드 되어 있어야 treeShaking
을 통해 최적화가 이루어집니다.
하지만 대다수의 npm 라이브러리들은 기본적으로 nodeJS
에서 동작이 되게 배포 하므로 ES modules
이 아닌 commonJS
로 빌드 되어 배포 되어져 있어 아쉽게도 거의 Treeshaking
을 통한 최적화는 이루어지지 않습니다.
그렇다고 방법이 없는 것은 아닙니다.
일부 용량이 큰 라이브러리의 경우 위와 같은 이유로 ES modules
로 빌드 된 버전을 따로 배포 하기도 합니다.
lodash
, lodash-es
둘 다 같은 라이브러리 이지만 빌드 된 모듈 시스템만 다르게 하여 배포 됩니다. webpack
을 사용 한다면 최적화를 위해 lodash-es
를 받아서 사용 할 수도 있겠습니다.
하지만 주의 할 점은 ES modules
로 빌드된 외부 라이브러리를 설치 할 경우 테스트 환경에서 오류가 나는 경우가 발생 할 수 있습니다. 테스트 런타임은 nodeJS
이기 때문에 ES modules
을 불러오지 못하기 때문입니다.
jest에는 babel-jest
라는 플러그인이 내장 되어 있어 보통 테스트 환경 일 때 바벨 설정에서 commonJS
로 트랜스파일되게 분기 처리를 합니다. 하지만 기본적으로 node_modules 폴더는 바벨 트랜스파일 대상에서 제외 대기 때문에 ES modules
로 빌드된 외부 패키지들이 오류를 발생하게 합니다.
따라서 ES modules
로 빌드된 외부 패키지들을 설치 하신후 테스트 환경을 구축하신다면 commonJS
로 빌드되게 설정을 해주어야하는 번거로운 단점이 존재 합니다
package.json
의sideEffects
속성이false
이어야 합니다.
sideEffects
옵션은 직접 내가 만든 패키지를 다른 개발 환경에서 트리쉐이킹이 되도록 최적화 하여 개발 하겠다면 도움이 되는 내용이지만 외부 라이브러리들을 최적화와는 무관한 내용이므로 직접 만드시는 상황이 아니면 넘어 가셔도 괜찮습니다.
내가 직접 프론트엔드에서 사용 할 공통 모듈을 개발하는데 treeShaking
을 통해 최적화 되도록 한다면 위의 첫번째 제약 조건에 나온 것 처럼 ES modules
로 빌드하여 배포하여야 합니다. 또 한가지 중요한 옵션인 sideEffects
가 있습니다.
sideEffects
옵션은 webpack
에서 설정하는 것이 아닌 배포되는 패키지의 package.json
에 정의 해주는 옵션입니다. 기본 값은 true
로 되어 있습니다. 이 옵션은 패키지 개발자가 webpack
에게 사용한 코드 이외의 코드는 제거해도 문제가 없는지 알려주는 옵션입니다.
만약 import 한 코드를 제외하고 전부 제거해도 괜찮으려면 각 코드간에 의존성 관리에 문제가 없어야 합니다.
예를 들어 A
라는 외부 패키지에서 a
, b
, c
라는 함수가 존재 할 때 a
함수만 import 해서 사용 할 경우
// A pacakge
export const a = () => { ... }
export const b = () => { ... }
export const c = () => { ... }
// index.js
import { a } from 'A'
a()
webpack
treeShaking
을 통해 b
와 c
는 삭제 될 거 라고 기대 할 수 있습니다.
하지만 만약 a
함수 내에서 b
나 c
에 의존성을 가지고 있다면 b
와 c
가 삭제 되었을 때 부작용이 발생하게 됩니다.
이러한 의존 관계를 번들링 과정에서 파싱하여 일일히 가려내기 어려우므로 이 패키지를 개발한 개발자에게 선택권을 줍니다. 번들링 과정중 import 한 코드를 제외한 나머지를 제거하더라도 문제가 되지 않는지 설정 하는 옵션이 sideEffects
입니다.
sideEffects
가 false
인 경우 코드를 제거해도 부작용이 없다는 뜻으로 treeShaking
이 이루어지게 됩니다.
앞선 제약 조건 때문에 treeShaking
을 통해 사용하지 않는 코드를 제거하는데는 한계가 있습니다.
하지만 패키지를 불러올 때 패키지의 이름만 적는 것이 아닌 패키지 내의 불러올 파일의 경로를 전부 적어주면 해당 파일과 의존하는 파일만 불러오게 됩니다.
lodash
전체를 다 불러옴import _ from 'lodash'
_.curry()
curry
와 curry
에 의존하는 파일만 불러온다.import curry from 'lodash/curry'
curry()
이렇게 사용할 경우 부분 불러오기가 가능하지만 단점도 있습니다.
위와 같은 단점이 있지만 1번
, 2번
문제는 babel
이나 webpack
설정을 통해 해결이 가능 하기도 합니다.
트랜스파일이나 번들링 시점에 import _ from 'lodash'
이렇게 작성 된 코드를 import curry from 'lodash/curry'
이렇게 바꾸어 주는 방식으로 해결 할 수 있습니다.
이것도 일부 자주 사용되는 라이브러리들은 바벨 플러그인이나 웹팩 플러그인으로 경로를 최적화 해주는 플러그인들이 npm에 배포 되어있어 잘 가져다 사용하면 됩니다.
번들링 최적화를 하기 위해 여러모로 삽질 해보고 실험 해본 결과는 이렇습니다.
외부 라이브러리들은 대체로 앞서 말한 제약조건 때문에 treeShaking
을 통해 최적화되는 패키지들은 거의 없습니다.
굳이 안되는 treeShaking
을 되게 하려는 것보다는 일부 용량이 큰 라이브러리(moment
, antd
)의 경우 공식 문서에 최적화 가이드를 작성해 놓은 경우가 종종 있습니다.
가이드의 지침에 따라서 웹팩이나 바벨의 설정을 따라서 해주는게 좋습니다.
여기까지 해주셨다면 다른 최적화 방안(SSR
, Code splitting
)에 시간 투자하는 것이 더 효과가 좋을 것입니다.
따라서 기억해야 할 중요한 사항은 다음과 같습니다.
긴 글 읽어 주셔서 감사합니다.